package com.stottlerhenke.simbionic.editor.gui;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import javax.swing.tree.DefaultMutableTreeNode;

import com.stottlerhenke.simbionic.common.parser.SB_ExpressionNode;
import com.stottlerhenke.simbionic.editor.SB_Behavior;
import com.stottlerhenke.simbionic.editor.SB_Binding;
import com.stottlerhenke.simbionic.editor.SB_ClassMethod;
import com.stottlerhenke.simbionic.editor.SB_ClassMethodParameter;
import com.stottlerhenke.simbionic.editor.SB_ErrorInfo;
import com.stottlerhenke.simbionic.editor.SB_Function;
import com.stottlerhenke.simbionic.editor.SB_Parameter;
import com.stottlerhenke.simbionic.editor.SB_Predicate;
import com.stottlerhenke.simbionic.editor.SimBionicEditor;
import com.stottlerhenke.simbionic.editor.parser.SB_BinaryOpNode;
import com.stottlerhenke.simbionic.editor.parser.SB_ErrorNode;
import com.stottlerhenke.simbionic.editor.parser.SB_FunctionNode;
import com.stottlerhenke.simbionic.editor.parser.SB_MethodNode;
import com.stottlerhenke.simbionic.editor.parser.SB_ParseNode;
import com.stottlerhenke.simbionic.editor.parser.SB_ParserEditorInterface;

public class SB_Rectangle extends SB_Element
{
    private static final long serialVersionUID = 2302585093L + 1006;

    static final Color _icolor = new Color(96, 255, 96);
    static final Color _fcolor = new Color(255, 96, 96);
    static final Color _acolor = new Color(254,255,79);
    static final Color _ccolor = new Color(78,65,254);

	private static final int SB_RECTANGLE_VERSION = 1;

	private static final int VERSION_FLAG = 2;

    public boolean _initial = false;
    public boolean _final = false;
    public boolean _catch = false;
    public boolean _always = false;
    protected boolean _complex = false;

    public void draw(Graphics2D g2)
    {
    	super.draw(g2);
    	if (_runningState == RUNNING)
    	{
    		g2.setPaint(_rcolor);
    		if (_complex)
    			g2.fillRect(_hrect.x - 1, _hrect.y - 1, _hrect.width + 1, _hrect.height + 1);
    		else
    			g2.fill(_hrect);
    	}
    	
        if (_complex)
            g2.setStroke(SB_Drawable.wideStroke);
        else
            g2.setStroke(SB_Drawable.stroke);
        if (_initial)
            g2.setPaint(_icolor);
        else if (_final)
            g2.setPaint(_fcolor);
        else if (_always)
        	g2.setPaint(_acolor);
        else if (_catch)
        	g2.setPaint(_ccolor);
        else
            g2.setPaint(Color.white);
        
        
        paintRectangle(g2);

        if (_labelMode == COMMENT_LABEL || _bindingsString == null)
          g2.drawString(_label, _labelOffsetX + _rect.x + SB_Drawable.border_x,
                        _rect.y + SB_Drawable.font_point + SB_Drawable.border_y);
        else
        {
        	if (_label.length() > 0)
        	{
        		g2.drawString(_bindingsString, _bindingsOffsetX + _rect.x + SB_Drawable.border_x,
        					  _rect.y + SB_Drawable.font_point + SB_Drawable.border_y - 1);
        		g2.drawString(_label, _labelOffsetX + _rect.x + SB_Drawable.border_x,
        					  _rect.y + SB_Drawable.font_point + SB_Drawable.border_y + 14);
        	}
        	else //Draw a possible multiline binding
        	{
        		String lines[] = _bindingsString.split(endline);
        		int availableHeight = _rect.height - SB_Drawable.border_y - SB_Drawable.font_point;
        		int lineHeight = availableHeight / lines.length;
        		
        		for(int x = 0; x < lines.length; x++)
        		{
	        		g2.drawString(lines[x], 
	        				_bindingsOffsetX + _rect.x + SB_Drawable.border_x,
	  					  	_rect.y + SB_Drawable.font_point + lineHeight * x + SB_Drawable.border_y);     
        		}
        	}
        }
        if(_isBreakpoint){
    		g2.setPaint(_breakcolor);
    		if(_breakpointEnabled){
    			g2.fillOval(_hrect.x+5, _hrect.y+18, 7, 7);
    		}
    		else{
    			g2.drawOval(_hrect.x+6, _hrect.y+19, 5, 5);
    		}
    	}
        g2.setPaint(Color.BLACK);
    }
    
    protected void paintRectangle(Graphics2D g2)
    {
    	g2.fill(_rect);
    	g2.setPaint(Color.black);
    	g2.draw(_rect);
    	
    }

    public void highlight(Graphics2D g2)
    {
    	g2.setPaint(_hcolor);
    	if (_runningState == NOT_RUNNING)
    	{
    		if (_complex)
    			g2.fillRect(_hrect.x - 1, _hrect.y - 1, _hrect.width + 1, _hrect.height + 1);
    		else
    			g2.fill(_hrect);
    	}
    	else
    	{
    		if (_complex)
    			g2.fillRect(_hrect.x - 3, _hrect.y - 3, _hrect.width + 6, _hrect.height + 6);
    		else
    			g2.fillRect(_hrect.x - 2, _hrect.y - 2, _hrect.width + 4, _hrect.height + 4);
    	}
    	
        g2.setPaint(Color.black);
    }

    public SB_Drawable containsPoint(Point point)
    {
        return _hrect.contains(point) ? this : null;
    }

    public boolean intersectsRect(Rectangle rect)
    {
        return _hrect.intersects(rect);
    }

    protected Point intersectionFromPoint(Point point)
    {
	Point center = new Point((int) _rect.getCenterX(), (int) _rect.getCenterY());
	double top, left, right, bottom;
	double t = 0;

	if (_rect.contains(point))
            return center;

	// equation for line: v = t(center - point) + center
	if (center.x != point.x)
	{
		left = _rect.getMinX();
		right = _rect.getMaxX();
		if (point.x < left)
			// find intersection along left
			t = (left - point.x)/(center.x - point.x);
		else if (point.x > right)
			// find intersection along right
			t = (right - point.x)/(center.x - point.x);
	}
	if (center.y != point.y)
	{
		bottom = _rect.getMaxY();
		top = _rect.getMinY();
		if (point.y > bottom)
			// find intersection along bottom, taking the point nearer to center
			t = Math.max(t, (bottom - point.y)/(center.y - point.y));
		else if (point.y < top)
			// find intersection along top, taking the point nearer to center
			t = Math.max(t, (top - point.y)/(center.y - point.y));
	}

	return new Point((int) (t*(center.x - point.x) + point.x),
		(int) (t*(center.y - point.y) + point.y));
    }

    
    protected void updateComplex(SimBionicEditor editor)
    {
      String name = SB_Catalog.extractFuncName(_expr);
      SB_ProjectBar projectBar = (SB_ProjectBar) ComponentRegistry.getProjectBar();
      _complex = projectBar._catalog.findBehavior(name) != null;
    }

    protected void compileSIM(BufferedWriter out, SB_Polymorphism poly, SB_ErrorInfo errorInfo, SB_MultiRectangle parentNode)
    {
      SimBionicEditor editor = poly._parent.getEditor();
      SB_OutputBar outputBar = SB_OutputBar.getInstance();
      SB_Output build = SB_OutputBar._build;

      try
      {
        out.write("\t\t\t\t\t" + _id);
        int nodeType = (_complex ? 1 : 0);
        
        if(this instanceof SB_MultiRectangle)
        	nodeType = SB_MultiRectangle.COMPOUND_ACTION;
        
        //Setup the component to assign errors to
        SB_Rectangle errorNode;
    	if (parentNode != null)
    		errorNode = parentNode;
    	else
    		errorNode = this;
        
        if (_final)
        {
           
          if( nodeType == SB_MultiRectangle.COMPOUND_ACTION)
          {
        	  errorInfo._ne++;
              build.addLine(new SB_Line("ERROR: Final node cannot be a compound action node.",
                      Color.red, poly, errorNode, null, SB_Line.ERROR));
              return;

          }
          if (_expr.length() > 0)
          {
            errorInfo._ne++;
            build.addLine(new SB_Line("ERROR: Final can only be specified on 'None' action.",
                                      Color.red, poly, errorNode, null, SB_Line.ERROR));
          }
          if (getBindingCount() > 0)
          {
            errorInfo._ne++;
            build.addLine(new SB_Line("ERROR: No bindings allowed on final.", Color.red,
                                      poly, errorNode, null, SB_Line.ERROR));
            return;
          }
          
          nodeType = 2;
        }
        
        out.write(" " + nodeType);
        
        int catchType = (_catch ? 1 : 0);
        int alwaysType = (_always ? 1 : 0);
        
        out.write(" 0 "+catchType+" "+alwaysType);	// JL: node mode, catch, always since v8, all zero for now
        out.newLine();

        SB_Catalog catalog = ((SB_ProjectBar) ComponentRegistry.getProjectBar())._catalog;
        String name = SB_Catalog.extractFuncName(_expr);
        Object func = null;	// can be either SB_Function or SB_ClassMethod
        SB_FunctionNode ftree= null;
        
        if (nodeType == 5)
        {
        	name += "...";
        }
        else
    	if (_expr.length() > 0)
    	{
    		String expr = catalog.constantReplace(_expr);
    		SB_ExpressionNode tree = SB_ParserEditorInterface.getParser().Parse( expr );

    		SB_ParseNode ptree = (SB_ParseNode) tree;
    		if (!ptree.IsValid())
    		{
    			SB_ErrorNode err = null;
    			while ((err = ptree.GetNextError(err)) != null)
    			{
    				errorInfo._ne++;
    	            build.addLine(new SB_Line("ERROR: " + err.GetMsg(), Color.red, poly, errorNode, null, SB_Line.ERROR));
    	        }
    			return;
    		}

    		boolean isAction = true;
    		if (tree instanceof SB_BinaryOpNode)
    		{
    			// maybe it's a method call
    			SB_BinaryOpNode opNode = (SB_BinaryOpNode) tree;
    			if (opNode != null)
    			{
    				// need to get the CFunction object to access its parameters
    				SB_MethodNode mtree = (SB_MethodNode) opNode.GetRightArg();
    				if (mtree != null)
    				{

    					// need the actual editor objects below
    					func = catalog.lookupClass( mtree.GetClass() ).lookupMethod( mtree.GetFunction(),
    																						mtree.GetOverload() );
    					ftree = mtree;
    				}
    			}
    		}
    		else
    		{
        		ftree = (SB_FunctionNode) tree;
    			// regular function call
    			func = catalog.findFunction(name);
    			if (func != null && (func instanceof SB_Predicate))
    				isAction = false;
    		}

    		if ((func == null) || !isAction)
    		{
    			errorInfo._ne++;
	            build.addLine(new SB_Line("ERROR: '" + name + "' is not an action, behavior, or class method.", Color.red, poly, errorNode, null, SB_Line.ERROR));
    			return;
    		}
    	}
    	else
    		name = "None";        
        
        String ab_text = (_complex ? "behavior" : "action");
    	if( nodeType == SB_MultiRectangle.COMPOUND_ACTION)
    		ab_text = "multi-action";
    	
        if(parentNode == null )
        {	
        	if (!_initial && poly.getConnectors().isUnreachable(this))
        	{
        		errorInfo._nw++;
        		build.addLine(new SB_Line("WARNING: '" + name + "' " + ab_text + " is unreachable.",
        				Color.blue, poly, errorNode, null, SB_Line.WARNING));
        	}
        	else if (_connectors.size() == 0)
        	{
        		if (!_final)
        		{
        			if (poly.getParent().getExec() == SB_Behavior.kExecOneTick)
        			{
        				errorInfo._ne++;
        				build.addLine(new SB_Line("ERROR: '" + name + "' " + ab_text + " is a dead-end node in a one-tick behavior.",
        						Color.red, poly, errorNode, null, SB_Line.ERROR));                                    
        			}
        			else
        			{
        				errorInfo._nw++;
        				build.addLine(new SB_Line("WARNING: '" + name + "' " + ab_text + " is terminal, non-final.",
        						Color.blue, poly, errorNode, null, SB_Line.WARNING));                  
        			}
        		}
        	}
        	else
        	{
        		if (_final)
        		{
        			errorInfo._nw++;
        			build.addLine(new SB_Line("WARNING: '" + name  + "' " + ab_text + " is non-terminal, final.",
        					Color.blue, poly, errorNode, null, SB_Line.WARNING));
        		}
        	}
        }

        int size_param_bindings = 0;
        DefaultMutableTreeNode params = null;

        if (func instanceof SB_Function)
        {
          params = catalog.findFuncNode(name);
          size_param_bindings = params.getChildCount();
        }
        else if (func instanceof SB_ClassMethod)
        	size_param_bindings = ((SB_ClassMethod)func).getParams().size();
        int size_var_bindings = getBindingCount();
        int size = size_param_bindings + size_var_bindings;
        
        if( nodeType == SB_MultiRectangle.COMPOUND_ACTION)
        {
        	size_var_bindings = 0;
        	size = 0;
        }
        
        out.write("\t\t\t\t\t\tBindings " + size);
        out.newLine();

        if (func != null)
        {
          for (int i = 0; i < size_param_bindings; ++i)
          {
            out.write("\t\t\t\t\t\t\t");
            String paramName = null;
            if (func instanceof SB_Function)
            {
            DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) params.getChildAt(i);
            paramName = ((SB_Parameter) childNode.getUserObject()).getName();
            }
            else if (func instanceof SB_ClassMethod)
            {
            	Object param = ((SB_ClassMethod)func).getParams().get(i);
            	paramName = ((SB_ClassMethodParameter)param).getName();
            }
            String param_expr = ftree.GetArg(i).ToString();

            // parse parameter expression
            SB_ParseNode arg_tree = ftree.GetArg(i);
            if (!arg_tree.IsValid())
            {
              SB_ErrorNode err = null;
              while ((err = arg_tree.GetNextError(err)) != null)
              {
                errorInfo._ne++;
                build.addLine(new SB_Line("ERROR: " + err.GetMsg(), Color.red, poly, errorNode, null, SB_Line.ERROR));
              }
            }

            int varKind = (_complex ? 1 : 2);
            out.write(varKind + " " + SB_ProjectBar.getPString(paramName) + " "
                      + SB_ProjectBar.getPString(param_expr));
            out.newLine();
          }
        }
        
        for (int i = 0; i < size_var_bindings; ++i)
        {
          out.write("\t\t\t\t\t\t\t");
          SB_Binding binding = getBinding(i);
          binding.compileSIM(out, poly, this, errorInfo);
        }

        compileTransIndices(out);

        if(nodeType == SB_MultiRectangle.COMPOUND_ACTION)
        {
        	((SB_MultiRectangle)this).compileNodes(out, poly, errorInfo);
        }
        
        if (!_final && nodeType != SB_MultiRectangle.COMPOUND_ACTION)
        {
          out.write("\t\t\t\t\t\t" + SB_ProjectBar.getPString(name));
          out.newLine();
        }
      }
      catch (IOException exception)
      {
        System.err.println("i/o exception");
      }
    }

    public void writeExternal(ObjectOutput out) throws IOException
    {
      super.writeExternal(out);

      out.writeByte(VERSION_FLAG);
      out.writeInt(SB_RECTANGLE_VERSION);
      out.writeBoolean(_initial);
      out.writeBoolean(_final);
      out.writeBoolean(_always);
      out.writeBoolean(_catch);
    }
    
    

    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException
    {
      super.readExternal(in);

     
      int versioned = in.read();
      
      //Check to see if a version int is next.
      //otherwise that bite was the initial flag
      if (versioned < 2)
      {
    	  _initial = (versioned == 0 ? false : true);//(Boolean) flag;//in.readBoolean();
    	  _final = in.readBoolean();
      }
      else  {
    	  readVersionedExternal(in);
      }
    }

    /**
     * Reads in the object data. It is proceeded by a version int. This can be used to specify what fields are written next.
     * Throws exception if there is an unknown version.
     * @param in
     * @throws IOException
     */
    private void readVersionedExternal(ObjectInput in) throws  IOException {
    	int version = in.readInt();
		if (version == 1)
		{
			_initial = in.readBoolean();
			_final = in.readBoolean();
			_always = in.readBoolean();
			_catch = in.readBoolean();
			
		}
		else {
			throw new IOException("SB_Rectangle - readExternal Data unknown version "+version);
		}
			
		
	}

	protected  void exportXML(BufferedWriter out) throws IOException
    {
      super.exportXML(out);
      out.write("\t\t\t\t\t\t\t<final>" + (_final ? 1 : 0) + "</final>");
      out.newLine();
    }

	public boolean isSpecial() {

		return _initial || _final || _always || _catch;
	}
}
